﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using NthDownload.Common.Plugins;
using NthDownload.Common.Utils;
using NthDownload.Contracts;
using NthDownload.Contracts.Plugins.Downloads;
using NthDownload.Composition;
using NthDownload.Contracts.Plugins.BandwidthProviders;

namespace NthDownload.BL.Serialization
{
    public class NthQueueXmlSerializationSerivce : ISerializationService<NthQueue, XElement>
    {
        #region Class Members

        private readonly IList<IBandwidthProvider> _bandwidthProviders;

        #endregion Class Members

        #region Constructor

        public NthQueueXmlSerializationSerivce(IList<IBandwidthProvider> bandwidthProviders)
        {
            _bandwidthProviders = bandwidthProviders;
            ImportService.TrySatisfyImports(this);
        }

        #endregion Constructor

        #region Properties

        /// <summary>
        /// The available download plugins
        /// </summary>
        [ImportMany(typeof(IDownloadPlugin), AllowRecomposition = true)]
        public List<IDownloadPlugin> DownloadPlugins { get; set; }

        /// <summary>
        /// The Synchronization Context of the executing thread
        /// </summary>
        [Import(typeof(SynchronizationContext), AllowRecomposition = true)]
        public SynchronizationContext UISynchronizationContext { get; set; }

        #endregion Properties

        #region Methods

        public XElement Serialize(NthQueue obj)
        {
            if (obj == null)
            {
                throw new Exception("Can't serialize a null queue.");
            }

            //
            // Load all download serialization services
            //
            Dictionary<string, IDownloadXmlSerializationService> downloadXmlSerializationServices = new Dictionary<string, IDownloadXmlSerializationService>();

            foreach (IDownloadPlugin downloadPlugin in DownloadPlugins)
            {
                string contractName = DownloadPluginContractNameService.CreateDownloadXmlSerializationServiceContractName(downloadPlugin.GUID);
                IDownloadXmlSerializationService downloadXmlSerializationService;
                if (ImportService.TryGetExportValue(contractName, out downloadXmlSerializationService))
                {
                    downloadXmlSerializationServices.Add(downloadPlugin.GUID, downloadXmlSerializationService);
                }
            }

            //
            // Build XML
            //
            XElement rootElement = new XElement("Data");

            rootElement.SetAttributeValue("GUID", obj.GUID);
            rootElement.SetAttributeValue("BandwidthProviderGUID", (obj.BandwidthProvider != null) ? obj.BandwidthProvider.GUID : "");
            rootElement.SetAttributeValue("Name", obj.Name);
            rootElement.SetAttributeValue("MaxRunningDownloads", obj.MaxRunningDownloads);
            rootElement.SetAttributeValue("MaxRetries", obj.MaxRetries);
            rootElement.SetAttributeValue("MaxThreads", obj.MaxThreads);
            rootElement.SetAttributeValue("StartDownloadsImmediately", obj.StartDownloadsImmediately);

            Func<string, IEnumerable<IDownload>, XElement> createDownloadsElementFunc = (newElementName, downloads) =>
            {
                XElement newElement = new XElement(newElementName);

                foreach (IDownload download in downloads)
                {
                    IDownloadXmlSerializationService downloadXmlSerializationService;
                    if (!downloadXmlSerializationServices.TryGetValue(download.ParentGUID, out downloadXmlSerializationService)) continue;

                    XElement downloadElement = new XElement("Download");
                    XElement downloadDataElement = downloadXmlSerializationService.Serialize(download);

                    downloadElement.SetAttributeValue("ParentGUID", download.ParentGUID);
                    downloadElement.SetAttributeValue("DataElementName", downloadDataElement.Name);
                    downloadElement.Add(downloadDataElement);

                    newElement.Add(downloadElement);
                }

                return newElement;
            };

            rootElement.Add(createDownloadsElementFunc.Invoke("ActiveDownloads", obj.ActiveDownloads));
            rootElement.Add(createDownloadsElementFunc.Invoke("InactiveDownloads", obj.InactiveDownloads));
            rootElement.Add(createDownloadsElementFunc.Invoke("ArchivedDownloads", obj.ArchivedDownloads));

            return rootElement;
        }

        public NthQueue Deserialize(XElement data)
        {
            //
            // Read data from element tree
            //
            NthQueue queue = null;
            UISynchronizationContext.Send(state => queue = new NthQueue(), null);
            if (queue == null)
            {
                throw new Exception("Queue initialization failed.");
            }

            Deserialize(data, queue);

            return queue;
        }

        public void Deserialize(XElement data, NthQueue obj)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }

            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }

            NthQueue queue = obj;

            //
            // Check if the queue has running downloads
            //
            if (queue.HasRunningDownloads)
            {
                throw new Exception("Can't update a queue that contains running downloads");
            }

            //
            // Load all download serialization services
            //
            Dictionary<string, IDownloadXmlSerializationService> downloadXmlSerializationServices = new Dictionary<string, IDownloadXmlSerializationService>();

            foreach (IDownloadPlugin downloadPlugin in DownloadPlugins)
            {
                string contractName = DownloadPluginContractNameService.CreateDownloadXmlSerializationServiceContractName(downloadPlugin.GUID);
                IDownloadXmlSerializationService downloadXmlSerializationService;
                if (ImportService.TryGetExportValue(contractName, out downloadXmlSerializationService))
                {
                    downloadXmlSerializationServices.Add(downloadPlugin.GUID, downloadXmlSerializationService);
                }
            }

            queue.GUID = (string)data.Attribute("GUID") ?? "";
            queue.BandwidthProvider = _bandwidthProviders.FirstOrDefault(b => b.GUID == (string)data.Attribute("BandwidthProviderGUID"));
            queue.Name = (string)data.Attribute("Name") ?? "";
            queue.MaxRunningDownloads = (int?)data.Attribute("MaxRunningDownloads") ?? 0;
            queue.MaxRetries = (int?)data.Attribute("MaxRetries") ?? 0;
            queue.MaxThreads = (int?)data.Attribute("MaxThreads") ?? 0;
            queue.StartDownloadsImmediately = (bool?)data.Attribute("StartDownloadsImmediately") ?? true;

            Func<string, List<IDownload>> createDownloadsFunc = downloadsElementName =>
            {
                XElement downloadsElement = XmlUtils.GetDecendant(data, downloadsElementName);
                List<IDownload> downloads = new List<IDownload>();

                foreach (XElement downloadElement in downloadsElement.Descendants("Download"))
                {
                    string parentGUID = (string)downloadElement.Attribute("ParentGUID") ?? "";
                    string dataElementName = (string)downloadElement.Attribute("DataElementName") ?? "";

                    IDownloadXmlSerializationService downloadXmlSerializationService;
                    if (!downloadXmlSerializationServices.TryGetValue(parentGUID, out downloadXmlSerializationService)) continue;

                    XElement downloadDataElement = downloadElement.Descendants(dataElementName).FirstOrDefault();

                    if (downloadDataElement != null)
                    {
                        downloads.Add(downloadXmlSerializationService.Deserialize(downloadDataElement));
                    }
                }

                return downloads;
            };

            queue.ActiveDownloads.Clear();
            List<IDownload> activeDownloads = createDownloadsFunc.Invoke("ActiveDownloads");
            UISynchronizationContext.Send(state => queue.AddToActiveDownloads(activeDownloads, true), null);

            queue.InactiveDownloads.Clear();
            List<IDownload> inactiveDownloads = createDownloadsFunc.Invoke("InactiveDownloads");
            UISynchronizationContext.Send(state => queue.AddToInactiveDownloads(inactiveDownloads, true), null);

            queue.ArchivedDownloads.Clear();
            List<IDownload> archivedDownloads = createDownloadsFunc.Invoke("ArchivedDownloads");
            UISynchronizationContext.Send(state => queue.AddToArchivedDownloads(archivedDownloads, true), null);
        }

        #endregion Methods
    }
}
